package ca.polymtl.rubikcube.CubeActivity;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.util.Log;
import ca.polymtl.rubikcube.CubeActivity.CubeSurfaceViewRenderer.AnimationCompleteCallback;
import ca.polymtl.rubikcube.CubeActivity.model.Rubik;
import ca.polymtl.rubikcube.CubeActivity.model.Side;
import ca.polymtl.rubikcube.CubeActivity.model.Rubik.RotationDirection;
import ca.polymtl.rubikcube.util.Rotation;
import ca.polymtl.rubikcube.util.Vect3f;

/**
 * This class is responsible for drawing a visual representation of a Rubik cube with OpenGL.
 * 
 * @author markys
 * 
 */
public class CubeRenderer {
	public static final String LOG_TAG = CubeRenderer.class.getSimpleName();
	/**
	 * The rubik cube we are drawing.
	 */
	Rubik rubik = null;
	Rubik rubikPicking;

	// The Renderer we will use to render each cubelet.
	final CubeletRenderer cubeletRenderer;

	/* Offsets of the cubelets position */
	final float[] _translates;// =

	public CubeRenderer(GL10 gl) {
		//this.rubik = rubik;
		this.rubikPicking = new Rubik();
		cubeletRenderer = new CubeletRenderer(gl);
		_translates = new float[3];
		_translates[0] = -1 * cubeletRenderer.getDimension();
		_translates[1] = 0 * cubeletRenderer.getDimension();
		_translates[2] = 1 * cubeletRenderer.getDimension();
	}

	AnimationCompleteCallback animationCompleteCallback = null;

	/**
	 * Renders a cube using OpenGL.
	 * 
	 * @param gl
	 *            The OpenGL context.
	 * @param cube
	 *            The cube to render.
	 */

		

	public void render(GL10 gl, boolean picking, boolean pickingSide) {
		if (rubik == null) {
			return;
		}
		/*
		 * Will increment the rotation angle if there is a face rotating.
		 */
		if (!picking) {
			incrementRotateFace();
		}
		/*
		 * If one face is in rotation, we draw three layers of 9 cubelets in a sequence perpendicular to that face. In
		 * other words, if a face is rotating, we will draw all the cubelets of this face at the same time
		 */

		/*
		 * If it is Front or Back
		 */
		if (isInRotation && (sideInRotation == Side.FRONT || sideInRotation == Side.BACK || sideInRotation == Side.MIDDLE_FB)) {
			/* Is it front or back */
			int rotatingZ = 0;
			if (sideInRotation == Side.FRONT) {
				rotatingZ = 2;
			}
			else if (sideInRotation == Side.BACK) {
				rotatingZ = 0;
			}
			else {
				rotatingZ = 1;
			}

			/*
			 * Draw the three cubelet layers
			 */
			for (int k = 0; k < Rubik.CUBEDIM; k++) {
				/*
				 * If it is the rotating layer.
				 */
				if (k == rotatingZ) {
					gl.glPushMatrix();

					Rotation r = new Rotation(angleInRotation, new Vect3f(0f, 0f, 1f));
					gl.glMultMatrixf(r.asFloatBuffer());
				}

				/* Draw the 9 cubelets of this layer */
				for (int j = 0; j < Rubik.CUBEDIM; j++) {
					for (int i = 0; i < Rubik.CUBEDIM; i++) {
						gl.glPushMatrix();
						gl.glTranslatef(_translates[i], _translates[j], _translates[k]);
						if (picking) {
							cubeletRenderer.render(gl, rubikPicking.cubeletAt(i, j, k), picking, pickingSide);
						}
						else {
							cubeletRenderer.render(gl, rubik.cubeletAt(i, j, k), picking, pickingSide);
						}
						gl.glPopMatrix();
					}
				}

				if (k == rotatingZ) {
					gl.glPopMatrix();
				}

			}
		}

		/*
		 * If it is Top or Bottom
		 */
		if (isInRotation && (sideInRotation == Side.UP || sideInRotation == Side.DOWN || sideInRotation == Side.MIDDLE_UD)) {
			/* Is it top of bottom */
			int rotatingY = 0;
			if (sideInRotation == Side.UP) {
				rotatingY = 2;
			}
			else if (sideInRotation == Side.DOWN) {
				rotatingY = 0;
			}
			else {
				rotatingY = 1;
			}

			for (int j = 0; j < Rubik.CUBEDIM; j++) {
				/*
				 * If it is the rotating layer.
				 */
				if (j == rotatingY) {
					gl.glPushMatrix();
					Rotation r = new Rotation(angleInRotation, new Vect3f(0f, 1f, 0f));
					gl.glMultMatrixf(r.asFloatBuffer());
				}

				for (int k = 0; k < Rubik.CUBEDIM; k++) {
					for (int i = 0; i < Rubik.CUBEDIM; i++) {
						gl.glPushMatrix();
						gl.glTranslatef(_translates[i], _translates[j], _translates[k]);
						if (picking) {
							cubeletRenderer.render(gl, rubikPicking.cubeletAt(i, j, k), picking, pickingSide);
						}
  						else {
							cubeletRenderer.render(gl, rubik.cubeletAt(i, j, k), picking, pickingSide);
						}
						gl.glPopMatrix();
					}
				}

				if (j == rotatingY) {
					gl.glPopMatrix();
				}
			}
		}

		/*
		 * If it is Right or Left
		 */
		if (isInRotation && (sideInRotation == Side.RIGHT || sideInRotation == Side.LEFT || sideInRotation == Side.MIDDLE_RL)) {
			/* Is it right or left */
			int rotatingX = 0;
			if (sideInRotation == Side.RIGHT) {
				rotatingX = 2;
			}
			else if (sideInRotation == Side.LEFT) {
				rotatingX = 0;
			}
			else {
				rotatingX = 1;
			}
			
			for (int i = 0; i < Rubik.CUBEDIM; i++) {

				/*
				 * If it is the rotating layer.
				 */
				if (i == rotatingX) {
					gl.glPushMatrix();
					Rotation r = new Rotation(angleInRotation, new Vect3f(1f, 0f, 0f));
					gl.glMultMatrixf(r.asFloatBuffer());
				}

				for (int k = 0; k < Rubik.CUBEDIM; k++) {
					for (int j = 0; j < Rubik.CUBEDIM; j++) {
						gl.glPushMatrix();
						gl.glTranslatef(_translates[i], _translates[j], _translates[k]);
						if (picking) {
							cubeletRenderer.render(gl, rubikPicking.cubeletAt(i, j, k), picking, pickingSide);
						}
						else {
							cubeletRenderer.render(gl, rubik.cubeletAt(i, j, k), picking, pickingSide);
						}
						gl.glPopMatrix();
					}
				}

				if (i == rotatingX) {
					gl.glPopMatrix();
				}
			}

		}

		/*
		 * No rotation, draw everything in any order
		 */
		if (!isInRotation) {
			for (int i = 0; i < Rubik.CUBEDIM; i++) {
				for (int j = 0; j < Rubik.CUBEDIM; j++) {
					for (int k = 0; k < Rubik.CUBEDIM; k++) {
						gl.glPushMatrix();
						gl.glTranslatef(_translates[i], _translates[j], _translates[k]);
						if (picking) {
							cubeletRenderer.render(gl, rubikPicking.cubeletAt(i, j, k), picking, pickingSide);
						}
						else {
							cubeletRenderer.render(gl, rubik.cubeletAt(i, j, k), picking, pickingSide);
						}
						gl.glPopMatrix();
					}
				}
			}
		}
	}

	/*
	 * For the animation of the rotation of a face.
	 */
	boolean isInRotation = false;
	float angleInRotation = 0f;
	Side sideInRotation = null;
	RotationDirection wayInRotation = null;
	float rotationInc = 0f;

	/**
	 * Initiate the rotation of a face.
	 * 
	 * @param side
	 *            Which face to rotate.
	 * @param way
	 *            Which way.
	 */
	public void startRotateFace(Side side, RotationDirection way) {
		/*
		 * Something is wrong in there, it turns the face the wrong way ! So, instead of fixing the real problem, hide
		 * it and go back watching TV.
		 */
		way = (way == RotationDirection.CW) ? RotationDirection.CCW : RotationDirection.CW;

		/* If there is a rotation going on, finish it instantly */
		if (isInRotation) {
			finishRotateFace();
		}

		Log.d(LOG_TAG, "Start rotation " + side + " " + way);

		isInRotation = true;
		angleInRotation = 0f;
		sideInRotation = side;
		wayInRotation = way;

		/* We want the rotation direction to be relative to a view from the exterior of the cube */
		if (side.ordinal() <= 2 && way == RotationDirection.CCW || side.ordinal() >= 3 && way == RotationDirection.CW) {
			rotationInc = 0.1f / 2;
		} else {
			rotationInc = -0.1f / 2;
		}
	}

	/**
	 * Slightly increase the angle of the rotating face to simulate the movement.
	 */
	public void incrementRotateFace() {
		if (!isInRotation) {
			return;
		}

		angleInRotation += rotationInc;

		/* Test if the rotation is complete */
		if (Math.abs(angleInRotation) >= Math.PI / 2.0) {
			finishRotateFace();
		}
	}

	/**
	 * Complete the rotation of the face.
	 */
	public void finishRotateFace() {
		if (!isInRotation) {
			return;
		}

		Log.d(LOG_TAG, "Stop rotation " + sideInRotation + " " + wayInRotation);

		rubik.rotateFace(sideInRotation, wayInRotation);

		isInRotation = false;
		angleInRotation = 0f;
		sideInRotation = null;
		wayInRotation = null;
		rotationInc = 0f;

		if (animationCompleteCallback != null) {
			animationCompleteCallback.animationCompleted();
		}
	}

	public void loadGLTexture(GL10 gl, Context context) {
		cubeletRenderer.loadGLTexture(gl, context);
	}

	public void setRubik(Rubik rubik) {
		this.rubik = rubik;

	}

	public void setAnimationCompleteCallback(AnimationCompleteCallback callback) {
		this.animationCompleteCallback = callback;
	}
}
